#include <cstdio>
#include <cstddef>



#include "transferdaemon.h"
#include "timestamp.h"
#include "visualserver.h"

void* ansInfoThread(void* _tran){
    TransferDaemon* tran = (TransferDaemon*)_tran;
    tran->answerInfo();
    return (void*) 0;
}

void* ansDataThread(void* _tran){
    TransferDaemon* tran = (TransferDaemon*)_tran;
    tran->answerData();
    return (void*) 0;
}

void* ansSyncThread(void* _tran){
    TransferDaemon* tran = (TransferDaemon*)_tran;
    tran->answerSync();
    return (void*) 0;
}


TransferDaemon::TransferDaemon()
{
    this->m_info_sock = -1;
    this->m_sync_sock = -1;
    this->m_data_sock = -1;
    this->m_info_port = DEFAULT_SERVER_INFO_PORT;
    this->m_sync_port = DEFAULT_SERVER_SYNC_PORT;
    this->m_data_port = DEFAULT_SERVER_DATA_PORT;
    this->m_working = false;
    sem_init(&this->m_info_mutex, 0 ,1);
    sem_init(&this->m_sync_mutex, 0, 1);
    sem_init(&this->m_data_mutex, 0, 1);
    this->m_data_switch = true;
    this->m_info_switch = true;

}

TransferDaemon::~TransferDaemon(){
    this->m_working = false;
    if(this->m_sync_sock != -1){
        close(this->m_sync_sock);
        pthread_cancel(this->m_ans_sync_thread);
    }
    if(this->m_info_sock != -1){
        close(this->m_info_sock);
        pthread_cancel(this->m_ans_info_thread);
    }
    if(this->m_data_sock != -1){
        close(this->m_data_sock);
        pthread_cancel(this->m_ans_data_thread);
    }

    printf("transfer closed\t");
    TimeStamp::now()->print();
}

void TransferDaemon::startAnswer(){
    if ((this->m_info_sock = this->establishSocket(this->m_info_port)) > 0
            && (this->m_sync_sock = this->establishSocket(this->m_sync_port)) > 0
            && (this->m_data_sock = this->establishSocket(this->m_data_port)) > 0){
        this->m_working = true;
        pthread_create(&this->m_ans_sync_thread, NULL, &ansSyncThread, (void*)this);
        pthread_create(&this->m_ans_info_thread, NULL, &ansInfoThread, (void*)this);
        pthread_create(&this->m_ans_data_thread, NULL, &ansDataThread, (void*)this);
    }else{
        fprintf(stderr, "Fail to start answer!\n");
    }
}

int TransferDaemon::establishSocket(int &port){
    struct sockaddr_in self_addr;
    int _sock = -1;
    int _port = port - 1;
    if ((_sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
        perror("Socket");
        return -1;
    }
    do{
        self_addr.sin_family = AF_INET;
        _port += 1;
        self_addr.sin_port = htons(_port);
        self_addr.sin_addr.s_addr = INADDR_ANY;
        bzero(&(self_addr.sin_zero), 8);
    }while (bind(_sock, (struct sockaddr *) &self_addr, sizeof(struct sockaddr))
            == -1);
    printf("Socket established on port %d\n", _port);
    port = _port;
    return _sock;
}

void TransferDaemon::answerInfo(){
    while(this->m_working){
        struct sockaddr_in addr;
        socklen_t addr_len = sizeof(addr);
        int packet_length;
        normal_request_t request;
        packet_length = recvfrom(this->m_info_sock, &request, sizeof(request), 0, (struct sockaddr*)&addr, &addr_len);
        if(packet_length == sizeof(normal_request_t)){
            ((VisualServer*)this->m_visual_server)->infoHandler(request, addr, addr_len);
        }
    }
}

void TransferDaemon::answerSync(){
    while(this->m_working){
        struct sockaddr_in addr;
        socklen_t addr_len = sizeof(addr);
        int packet_length;
        normal_request_t request;
        packet_length = recvfrom(this->m_sync_sock, &request, sizeof(request), 0, (struct sockaddr*)&addr, &addr_len);
        if(packet_length == sizeof(normal_request_t)){
            ((VisualServer*)this->m_visual_server)->syncHandler(request, addr, addr_len);
        }
    }
}

void TransferDaemon::answerData(){
    while(this->m_working){
        struct sockaddr_in addr;
        socklen_t addr_len = sizeof(addr);
        int packet_length;
        normal_request_t request;
        packet_length = recvfrom(this->m_data_sock, &request, sizeof(request), 0, (struct sockaddr*)&addr, &addr_len);
        if(packet_length == sizeof(normal_request_t)){
            ((VisualServer*)this->m_visual_server)->dataHandler(request, addr, addr_len);
        }
    }
}


void TransferDaemon::sendData(void *data, int size, sockaddr_in addr, socklen_t addr_len){
    int sent;
    if(this->m_data_switch){
        sem_wait(&this->m_data_mutex);
        addr.sin_port = htons(DEFAULT_CLIENT_DATA_PORT);
        sent = sendto(this->m_data_sock, data, size, 0, (struct sockaddr*)&addr, addr_len);
        if(sent != size){
            fprintf(stderr, "Error occurred in sendData!\n");
        }
        sem_post(&this->m_data_mutex);
    }
}

void TransferDaemon::sendInfo(void *data, int size, sockaddr_in addr, socklen_t addr_len){
    int sent;
    if(this->m_info_switch){
        sem_wait(&this->m_info_mutex);
        sent = sendto(this->m_info_sock, data, size, 0, (struct sockaddr*)&addr, addr_len);
        if(sent != size){
            fprintf(stderr, "Error occurred in sendInfo!\n");
        }
        sem_post(&this->m_info_mutex);
    }
}


void TransferDaemon::sendSync(void *data, int size, sockaddr_in addr, socklen_t addr_len){
    int sent;
    sem_wait(&this->m_sync_mutex);
    sent = sendto(this->m_sync_sock, data, size, 0, (struct sockaddr*)&addr, addr_len);
    if(sent != size){
        perror("sendSync:");
        fprintf(stderr, "Error occurred in sendSync! %d/%d\n", sent, size);
    }
    sem_post(&this->m_sync_mutex);

}
